import javax.microedition.lcdui.Graphics;

/**
 *<h1> Widgets.java</h1>
 * <h2>A simple, but flexible widget system for mobile games.</h2>
 * <h3>07 July 2008</h3>
 * 
 * Goals:
 * <p>
 * <ol>
 *  	<li>Small footprint, both for JAR and HEAP</li>
 *  	<li>Flexible and easy to plug into existing code</li> 
 *  	<li>Skinable and configurable through external files</li>
 *  </ol>
 * </p>
 * 
 * Layout principles:
 * <p>
 * <ol>
 * 	<li>Two types of widgets. Screens and controls.</li>
 * 
 * 	<li>Containers can hold lists of widgets and are considered their parent for default methods and layout handling.</li>
 * 	
* 		<li>Widgets in a screen flow in a vertical direction</li>
 
*  	<li>No absolute positioning or absolute sizes</li>
 *  	Sizes and positions are calculated based on screen size and  order.
 *    <br/>
 *    
 *    <li>Vertical scrollers are automatically shown for screens that outgrow the display size.</li>
 *  </ol>
 * </p>
 * 
 *  Input handling principles:
 * <p>
 * <ol>
 * 	<li>Widget UI is modal</li>
 * 	I.e. if a widget screen is active all input is being handled by the widget API and not send to the game. 
 * 	<li>Three independent types of input. </li>
 * 	Softkey handling, focus movement, key handling.
 * 	<li>Two types of widgets in terms of user interaction</li>
 * 	Focusable (interactive) and visual only.
 * 	<li>Two types of widgets in terms of internal data</li>
 * 	Dynamic (they get changed in time, either by the user or by the game logic) and static.
 * 	<li>There is only one widget focused at any given moment.</li>
 *  </ol>
 * </p>
 * 
 * @author Plamen Dragozov plamen@popcap.com
 *
 */
public class Widgets implements Constants
{

	static final int NUM_KEYS = Input.KEY_FLAG_0 | Input.KEY_FLAG_1 | Input.KEY_FLAG_2 | Input.KEY_FLAG_3 | Input.KEY_FLAG_4 | Input.KEY_FLAG_5 | Input.KEY_FLAG_6 | Input.KEY_FLAG_7 | Input.KEY_FLAG_8 | Input.KEY_FLAG_9;
	static final int NUM_PAD = Input.KEY_FLAG_STAR | Input.KEY_FLAG_POUND | NUM_KEYS;
	static int m_nScreenId = -1;
	static int m_nScreenType = -1;
	static int m_nScreenWidgetsOffset = -1;

	static int m_nScreenPaddingTop, m_nScreenPaddingBottom;
	static int m_nScreenWidth;
	static int m_nScreenHeight;

	//maximum widgets in a screen
	//the ending coordinate of a widget
	static int[] m_WidgetEnd = new int[WidgetsImpl.MAX_WIDGETS_PER_SCREEN];
	static int[] m_WidgetWidth = new int[WidgetsImpl.MAX_WIDGETS_PER_SCREEN];
	static int m_nWidgetsOffsetX = 0;

	static final StringBuffer[] WIDGET_STATE = new StringBuffer[WidgetsImpl.MAX_WIDGETS_PER_SCREEN];

	static int m_nWidgetsCount = 0;
	static int m_nFirstWidget = 0;
	static int m_nLastWidget = 0;
	static int m_nFocusedWidget;

	static int m_nWidgetsMargin;
	static int m_nDrawingWidth;
	static int m_nDrawingHeight;
	static int m_nFrameCounter;

	static int getWidgetIndex(int widgetId)
	{
		for(int i = 0;i < m_nWidgetsCount;i++ )
		{
			if(WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset + i) == widgetId)
			{
				return i;
			}
		}
		return -1;
	}

	static int getWidgetId(int widgetIndex)
	{
		if(widgetIndex >= 0 && widgetIndex < m_nWidgetsCount)
		{
			return WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset + widgetIndex);
		}
		return -1;
	}

	static int getWidgetType(int widgetIndex)
	{
		final int id = getWidgetId(widgetIndex);
		if(id >= 0)
		{
			final int offset = WidgetsImpl.WIDGET_OFFSET.charAt(id);
			return WidgetsImpl.WIDGET_DATA.charAt(offset);
		}
		return -1;
	}

	/**
	 * Sets and initializes a UI screen.
	 * 
	 * @param screenId The id of the screen. Has to be a top-level container. 
	 */
	static void createScreen(int screenId, int w, int h)
	{
		m_nFrameCounter = 0;
		m_nScreenId = screenId;
		m_nScreenWidgetsOffset = WidgetsImpl.SCREEN_OFFSET.charAt(screenId);
		m_nScreenType = WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset++ );
		m_nWidgetsCount = WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset++ );

		m_nScreenPaddingTop = WidgetsImpl.screenPaddingTop(m_nScreenId, m_nScreenType);
		m_nScreenPaddingBottom = WidgetsImpl.screenPaddingBottom(m_nScreenId, m_nScreenType);

		m_nWidgetsMargin = WidgetsImpl.screenWidgetsMargin(m_nScreenId, m_nScreenType);

		m_nScreenHeight = h;
		m_nScreenWidth = w;

		m_nFocusedWidget = -1;
		initChildren();
		m_nDrawingWidth = w - 2 * m_nWidgetsOffsetX;
		m_nDrawingHeight = h - m_nScreenPaddingTop - m_nScreenPaddingBottom;
		//#ifdef DEBUG
//		System.out.println(m_nDrawingHeight +"="+ h +"-"+ m_nScreenPaddingTop +"-"+ m_nScreenPaddingBottom);
		//#endif
		m_nFirstWidget = 0;
		m_nLastWidget = 0;
		for(int i = m_nWidgetsCount - 1;i >= 0;i-- )
		{
			if(m_WidgetEnd[i] < m_nDrawingHeight)
			{
				m_nLastWidget = i;
				break;
			}
		}
		WidgetsImpl.screenInit(m_nScreenId, m_nScreenType);
	}

	/**
	 *Go through all child widgets and initialize them.
	 * 
	 */
	private static void initChildren()
	{
		int childId, childType, childOffset;
		int y = 0;
		int maxWidth = 0;
		for(int i = 0;i < m_nWidgetsCount;i++ )
		{
			childId = WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset + i);
			childOffset = WidgetsImpl.WIDGET_OFFSET.charAt(childId);
			childType = WidgetsImpl.WIDGET_DATA.charAt(childOffset++ );
			y += WidgetsImpl.widgetHeight(childId, childType, m_nScreenId, i) + m_nWidgetsMargin;
			m_WidgetEnd[i] = y;
			m_WidgetWidth[i] = WidgetsImpl.widgetWidth(childId, childType, m_nScreenId, i);
			if(m_WidgetWidth[i] > maxWidth)
			{
				maxWidth = m_WidgetWidth[i];
				//#ifdef DEBUG
				System.out.println(childId + "/"+ childType+". NEW MAX WIDTH: " + maxWidth);
				//#endif
			}

			//Initialize dynamic data
			if(WidgetsImpl.isDynamic(childId, childType, m_nScreenId, i))
			{
				if(WIDGET_STATE[i] == null)
				{
					for(int j = i + 1;j < m_nWidgetsCount;j++ )
					{
						if(WIDGET_STATE[j] != null)
						{
							//try reusing the next available buffer
							WIDGET_STATE[i] = WIDGET_STATE[j];
							WIDGET_STATE[j] = null;
							WIDGET_STATE[i].delete(0, WIDGET_STATE[i].length());
							break;
						}
					}
					if(WIDGET_STATE[i] == null)
					{
						//no luck, create new
						WIDGET_STATE[i] = new StringBuffer(WidgetsImpl.INITIAL_STATE_DATA_SIZE);
					}
				}
				else
				{
					//reuse the buffer
					WIDGET_STATE[i].delete(0, WIDGET_STATE[i].length());
				}
			}
			m_nWidgetsOffsetX = (m_nScreenWidth - maxWidth) / 2;
			//#ifdef DEBUG
			System.out.println("Offset x: " + m_nWidgetsOffsetX + "=("+m_nScreenWidth+"-"+maxWidth +")/2");
			//#endif
			WidgetsImpl.widgetInit(childId, childType, m_nScreenId, i);

			//set the focus on the first interactive widget
			if(m_nFocusedWidget < 0)
			{
				if(WidgetsImpl.isFocusable(childId, childType, m_nScreenId, i))
				{
					focus(i);
				}
				//				else if(i == m_nWidgetsCount - 1)
				//				{
				//					//no focusable widgets?!
				//					focus( -1);
				//				}
			}
		}
	}

	/**
	 * 
	 * @param g
	 */
	static void drawScreen(Graphics g, int x, int y)
	{
		if(m_nScreenId >= 0)
		{
			WidgetsImpl.screenDrawBackground(m_nScreenId, m_nScreenType, g, x, y, m_nScreenWidth, m_nScreenHeight);
			drawWidgets(g, x + m_nWidgetsOffsetX, y + m_nScreenPaddingTop);
			WidgetsImpl.screenDrawScroller(m_nScreenId, m_nScreenType, g);
			WidgetsImpl.screenDrawForeground(m_nScreenId, m_nScreenType, g, x, y, m_nScreenWidth, m_nScreenHeight);
		}
	}

	static void drawWidgets(Graphics g, int x, int y)
	{
		int childId, childType, childOffset;
		final int startY = m_nFirstWidget == 0 ? 0 : m_WidgetEnd[m_nFirstWidget - 1];
		int yPos = y;
		for(int i = m_nFirstWidget;i <= m_nLastWidget;i++ )
		{
			childId = WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset + i);
			childOffset = WidgetsImpl.WIDGET_OFFSET.charAt(childId);
			childType = WidgetsImpl.WIDGET_DATA.charAt(childOffset++ );
			WidgetsImpl.widgetDraw(childId, childType, m_nScreenId, i, g, x, yPos);
			yPos = y + m_WidgetEnd[i] - startY;
		}
	}

	/**
	 * 
	 */
	static void moveDown()
	{
		if(m_nFocusedWidget < 0 || m_nFocusedWidget < m_nWidgetsCount - 1)//we are not at the last 
		{
			if(m_nFocusedWidget >= 0)
			{
				if(m_nFocusedWidget <= m_nLastWidget)
				{
					//find next focusable
					int childIndex, childId, childType, childOffset;
					for(int i = m_nFocusedWidget + 1;i < m_nWidgetsCount;i++ )
					{
						childIndex = m_nScreenWidgetsOffset + i;
						childId = WidgetsImpl.SCREEN_DATA.charAt(childIndex);
						childOffset = WidgetsImpl.WIDGET_OFFSET.charAt(childId);
						childType = WidgetsImpl.WIDGET_DATA.charAt(childOffset++ );
						if(WidgetsImpl.isFocusable(childId, childType, m_nScreenId, i))
						{
							focus(i);
							if(i <= m_nLastWidget)
							{
								//nothing more to do
								return;
							}
							else
							{
								m_nLastWidget = i;
								break;
							}
						}
						else if(i == m_nWidgetsCount - 1)
						{
							//no more focusable widgets, scroll down
							if(m_nLastWidget < m_nWidgetsCount - 1)
							{
								m_nLastWidget++ ;
							}
							break;
						}
					}
				}
				else
				{
					//very special case when the first focusable widget is on the 2nd screen!?
					m_nLastWidget = m_nFocusedWidget;
				}
			}
			else if(m_nLastWidget < m_nWidgetsCount - 1)
			{
				//just scroll down, there are no focusable widgets on the screen
				m_nLastWidget++ ;
			}

			final int lastEnd = m_WidgetEnd[m_nLastWidget];
			//update m_nFirstWidget
			for(int i = m_nLastWidget - 1;i >= 0;i-- )
			{
				if(lastEnd - (i > 0 ? m_WidgetEnd[i - 1] : 0) <= m_nDrawingHeight)
				{
					//we can fit the next one
					m_nFirstWidget = i;
				}
				else
				{
					break;
				}
			}
		}
	}

	/**
	 * 
	 */
	static void moveUp()
	{
		if(m_nFocusedWidget < 0 || m_nFocusedWidget > 0)//we are not at the first 
		{
			if(m_nFocusedWidget >= 0)
			{
				//find next focusable
				int childIndex, childId, childType, childOffset;
				for(int i = m_nFocusedWidget - 1;i >= 0;i-- )
				{
					childIndex = m_nScreenWidgetsOffset + i;
					childId = WidgetsImpl.SCREEN_DATA.charAt(childIndex);
					childOffset = WidgetsImpl.WIDGET_OFFSET.charAt(childId);
					childType = WidgetsImpl.WIDGET_DATA.charAt(childOffset++ );
					if(WidgetsImpl.isFocusable(childId, childType, m_nScreenId, i))
					{
						focus(i);
						if(i > m_nFirstWidget)
						{
							//nothing more to do
							return;
						}
						else
						{
							m_nFirstWidget = i;
							break;
						}
					}
					else if(i == 0)
					{
						if(m_nFirstWidget > 0)
						{
							m_nFirstWidget-- ;
						}
						break;
					}
				}
			}
			else if(m_nFirstWidget > 0)
			{
				m_nFirstWidget-- ;
			}

			final int firstStart;
			if(m_nFirstWidget > 0)
			{
				firstStart = m_WidgetEnd[m_nFirstWidget - 1];

			}
			else
			{
				firstStart = 0;
			}
			//update the last
			for(int i = m_nFirstWidget + 1;i < m_nWidgetsCount;i++ )
			{
				if(m_WidgetEnd[i] - firstStart - m_nWidgetsMargin <= m_nDrawingHeight)
				{
					//we can fit this one
					m_nLastWidget = i;
				}
				else
				{
					break;
				}
			}

		}
	}

	/**
	 * 
	 * @param keyPressed
	 * @param keyHold
	 */
	static void update()
	{
		if(m_nScreenId >= 0)
		{
			m_nFrameCounter++ ;
			WidgetsImpl.screenUpdate(m_nScreenId, m_nScreenType);
			int childId, childType;
			for(int i = 0;i < m_nWidgetsCount;i++ )
			{
				childId = WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset + i);
				childType = WidgetsImpl.WIDGET_DATA.charAt(WidgetsImpl.WIDGET_OFFSET.charAt(childId));
				WidgetsImpl.widgetUpdate(childId, childType, m_nScreenId, i);
			}
			if(Game.m_nLastPressed != 0)
			{
				final int id = WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset + (m_nFocusedWidget >= 0 ? m_nFocusedWidget : m_nFirstWidget));
				final int offset = WidgetsImpl.WIDGET_OFFSET.charAt(id);
				final int type = WidgetsImpl.WIDGET_DATA.charAt(offset);

				if(WidgetsImpl.screenModal(m_nScreenId, m_nScreenType))
				{
					return;
				}
				else if(((Game.m_nLastPressed & Input.KEY_FLAG_SOFTKEY_RIGHT) != 0) || ((Game.m_nLastPressed & Input.KEY_FLAG_SOFTKEY_LEFT) != 0))
				{
					//Softkeys have highest priority

					WidgetsImpl.screenOnSoftkey(m_nScreenId, type);
				}
				else if(((Game.m_nLastPressed & NUM_PAD) != 0))
				{
					WidgetsImpl.widgetOnKey(id, type, m_nScreenId, m_nFocusedWidget);
				}
				else if((Game.m_nLastPressed & Input.KEY_FLAG_FIRE) != 0)
				{
					WidgetsImpl.widgetOnFire(id, type, m_nScreenId, m_nFocusedWidget);
				}
				else if(((Game.m_nLastPressed & Input.KEY_FLAG_UP) != 0) || ((Game.m_nLastPressed & Input.KEY_FLAG_DOWN) != 0) || ((Game.m_nLastPressed & Input.KEY_FLAG_LEFT) != 0) || ((Game.m_nLastPressed & Input.KEY_FLAG_RIGHT) != 0))
				{
					WidgetsImpl.widgetOnDpad(id, type, m_nScreenId, m_nFocusedWidget);
				}
			}
		}
	}

	/**
	 * Hides the widgets screen.
	 */
	static void hide()
	{
		m_nScreenId = -1;
		m_nFocusedWidget = -1;
	}

	/**
	 * Set the focus
	 * @param id
	 */
	static void focus(int index)
	{
		if(m_nFocusedWidget != index)
		{
			if(m_nFocusedWidget >= 0)
			{
				final int id = WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset + m_nFocusedWidget);
				final int offset = WidgetsImpl.WIDGET_OFFSET.charAt(id);
				final int type = WidgetsImpl.WIDGET_DATA.charAt(offset);
				WidgetsImpl.widgetOnFocusLost(id, type, m_nScreenId, m_nFocusedWidget);
			}
			m_nFocusedWidget = index;
			if(m_nFocusedWidget >= 0)
			{
				final int id = WidgetsImpl.SCREEN_DATA.charAt(m_nScreenWidgetsOffset + m_nFocusedWidget);
				final int offset = WidgetsImpl.WIDGET_OFFSET.charAt(id);
				final int type = WidgetsImpl.WIDGET_DATA.charAt(offset);
				WidgetsImpl.widgetOnFocus(id, type, m_nScreenId, m_nFocusedWidget);
			}
		}
	}
}
